/**
 * RetryOnExceptionPolicyInterceptorSpec.java
 * 
 * Created on Jul 3, 2008 at 3:39:52 PM
 */
package org.lego4j;

import static org.junit.Assert.assertSame;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.stub;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicReference;

import org.aopalliance.intercept.MethodInvocation;
import org.junit.Before;
import org.junit.Test;
import org.mockito.MockitoAnnotations.Mock;

/**
 * Specification for {@link RetryOnExceptionPolicyInterceptor}.
 *
 * @author <a href="alexguev@gmail.com">Alexei Guevara</>
 * @version $Revision:$ updated on $Date:$ by $Author:$
 */
public class RetryOnExceptionPolicyInterceptorSpec extends MockitoSpec {
    
    private RetryOnExceptionPolicyInterceptor myInterceptor;

    @Mock private IRetryOnExceptionPolicy myPolicyWithLongDelay;

    @Before
    public void createInterceptor() {
        this.myInterceptor = new RetryOnExceptionPolicyInterceptor() {
            @Override
            protected IRetryOnExceptionPolicy createPolicy( RetryOnException theAnnotation) {
                return createMockRetryOnExceptionPolicy();
            }
        };    
    }
    
    private IRetryOnExceptionPolicy createMockRetryOnExceptionPolicy() {
    	stub(myPolicyWithLongDelay.isExceptionRecoverable(isA(Throwable.class))).toReturn(true);
    	stub(myPolicyWithLongDelay.nextDelay(anyLong(), anyInt())).toReturn(Long.MAX_VALUE);
        return this.myPolicyWithLongDelay;
    }

    @Test
    public void shouldThrowOriginalExceptionIfThreadInterruptedWhileWaitingToRetry() throws InterruptedException {
        final AtomicReference<Throwable> exThrown = new AtomicReference<Throwable>( null ); 
        
        final Throwable originalException = new Exception("Big Bang!");
        
        Thread thread = new Thread( new Runnable() {
            public void run() {
                try {
                    RetryOnExceptionPolicyInterceptorSpec.this.myInterceptor.invoke(createMethodInvocationThatThrowsException(originalException));
                } 
                catch (Throwable e) {
                    exThrown.set(e);
                }
            }
        } );
        
        thread.start();

        thread.interrupt();
        
        //wait for thread to complete execution
        thread.join();
        
        assertSame( originalException, exThrown.get() );
    }

    private MethodInvocation createMethodInvocationThatThrowsException( final Throwable theE ) {
        return new MethodInvocation() {
            public Method getMethod() {
                try {
					return this.getClass().getMethod("getMethod", new Class[]{});
				} catch (SecurityException e) {
					return null;
				} catch (NoSuchMethodException e) {
					return null;
				}
            }
            public Object[] getArguments() {
                return null;
            }
            public AccessibleObject getStaticPart() {
                return null;
            }
            public Object getThis() {
                return null;
            }
            public Object proceed() throws Throwable {
                throw theE;
            }
        };
    }

}
